using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;

namespace Pinwheel.LSerialize
{
    public unsafe partial struct LStreamReader : IDisposable
    {
        public static LStreamReader* Default;
        
        private LStreamBuffer* buffer;
        public LStreamBuffer* Buffer => buffer;

        static LStreamReader()
        {
            Default = Native.Current.AllocAndClear<LStreamReader>();
            Default->buffer = LStreamBuffer.Create(); 
        }

        public LStreamReader(int size = LStreamBuffer.DefaultSize)
        {
            buffer = LStreamBuffer.Create(size);
        }
        
        public LStreamReader(byte* source, int size)
        {
            buffer = LStreamBuffer.Create(source, size);
        }
        
        public LStreamReader(LStreamBuffer* source)
        {
            buffer = source;
        }
        
        public void Dispose()
        {
            // buffer->Dispose();
            Native.Current.Free(buffer); 
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int Seek(int offset, SeekOrigin loc) => Buffer->Seek(offset, loc);
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte[] GetBuffer() => Buffer->GetBuffer();
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ILFormatter<T> GetFormatter<T>() => LFormatterProvider.GetFormatter<T>();
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ILFormatter GetFormatter(Type type) => LFormatterProvider.GetFormatter(type);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte ReadByte()
        {
            return Buffer->PopByte();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public UInt16 ReadUInt16()
        {
            // return (UInt16)((UInt16)Buffer->PopByte() | 
            //                 ((UInt16)Buffer->PopByte()) << 8);
            UInt16 result = default;
            ReadUnmanagedValue(ref result);
            return result; 
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public UInt32 ReadUInt32()
        {
            // return (UInt32) ((UInt32)Buffer->PopByte() | 
            //                  ((UInt32)Buffer->PopByte()) << 8 |
            //                  ((UInt32)Buffer->PopByte()) << 16 |
            //                  ((UInt32)Buffer->PopByte()) << 24);
            UInt32 result = default;
            ReadUnmanagedValue(ref result);
            return result; 
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public UInt64 ReadUInt64()
        {
            // return (UInt64) ((UInt64)Buffer->PopByte() | 
            //                  ((UInt64)Buffer->PopByte()) << 8 |
            //                  ((UInt64)Buffer->PopByte()) << 16 |
            //                  ((UInt64)Buffer->PopByte()) << 24 |
            //                  ((UInt64)Buffer->PopByte()) << 32 |
            //                  ((UInt64)Buffer->PopByte()) << 40 |
            //                  ((UInt64)Buffer->PopByte()) << 48 |
            //                  ((UInt64)Buffer->PopByte()) << 56);

            UInt64 result = default;
            ReadUnmanagedValue(ref result);
            return result;
        }
        
        // [MethodImpl(MethodImplOptions.AggressiveInlining)]
        // public UInt128 ReadUInt128()
        // {
        //     return ReadUInt64() | (UInt128)ReadUInt64() << 64;
        // } 
                
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void ReadUnmanagedValue<T>(ref T value) where T : unmanaged
        {
            int length = sizeof(T);
            fixed (void* ptr = &value)
            {
                Buffer->EnsureTail(length);
                Native.Utils.Copy(ptr, Buffer->Current, length);
                Buffer->Seek(length, SeekOrigin.Current);    
            }
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public unsafe T[] ReadUnmanagedArray<T>() where T : unmanaged
        {
            Buffer->EnsureTail(sizeof(uint));
            uint length = ReadUInt32();
            if (0 == length) return Array.Empty<T>();
            var arr = new T[length];
            var size = (int) length * sizeof(T);
            void* ptr = Unsafe.AsPointer(ref arr);
            Buffer->EnsureTail(size);
            Native.Utils.Copy(ptr, Buffer->Current, size);
            Buffer->Seek(size, SeekOrigin.Current);
            return arr;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public unsafe T[] UnsafeReadUnmanagedArray<T>()
        {
            Buffer->EnsureTail(sizeof(uint));
            uint length = ReadUInt32();
            if (0 == length) return Array.Empty<T>();
            var arr = new T[length];
            var size = (int) length * Unsafe.SizeOf<T>();
            void* ptr = Unsafe.AsPointer(ref arr);
            Buffer->EnsureTail(size);
            Native.Utils.Copy(ptr, Buffer->Current, size);
            Buffer->Seek(size, SeekOrigin.Current);
            return arr;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte[] ReadBytes(int length = 0)
        {
            if (0 == length)
            {
                Buffer->EnsureTail(sizeof(uint));
                length = (int)ReadUInt32();
            }
            return Buffer->PopBytes(length);
        }
        
        // [MethodImpl(MethodImplOptions.AggressiveInlining)]
        // public T[] ReadBytes<T>(int length = 0) where T : unmanaged
        // {
        //     if (0 == length)
        //     {
        //         Buffer->EnsureTail(sizeof(uint));
        //         length = (int)ReadUInt32();
        //     }
        //     return Buffer->PopBytes<T>(length);
        // }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public string ReadString()
        {
            Buffer->EnsureTail(sizeof(uint));
            var length = (int)ReadUInt32();
            var result = Encoding.UTF8.GetString(Buffer->Current, length);
            Buffer->Seek(length, SeekOrigin.Current);
            return result;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Reset(int size) => Buffer->Reset(size);
    }
}